﻿using System;
using System.Collections.Generic;
using Microsoft.Exchange.WebServices.Data;
using System.IO;
using System.DirectoryServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Configuration;

namespace FolderCalendar
{
    public class Program
    {
        [STAThread]
        static void Main()
        {
            try
            {
                TaskManager.CheckTask();

                // Configures Admin User Bindings
                ExchangeVersion sourceVersion = GetExchangeVer("sourceESversion");
                ExchangeVersion destinationVersion = GetExchangeVer("destinationESversion");


                ExchangeService serviceS = new ExchangeService(sourceVersion);
                serviceS.Credentials = new WebCredentials(ConfigurationManager.AppSettings.Get("sourceAdminAccountName")
                                                          , ConfigurationManager.AppSettings.Get("sourceAdminAccountPassword"));
                serviceS.Url = new Uri(ConfigurationManager.AppSettings.Get("sourceURI"));

                ExchangeService serviceD = new ExchangeService(destinationVersion);
                serviceD.Credentials = new WebCredentials(ConfigurationManager.AppSettings.Get("destinationAdminAccountName")
                                                          , ConfigurationManager.AppSettings.Get("destinationAdminAccountPassword"));
                serviceD.Url = new Uri(ConfigurationManager.AppSettings.Get("destinationURI"));


                UsersCollection users = GetUsers();
                SynchronizeCalendars(serviceS, serviceD, ref users);
                SaveState(users);
            }
            catch (Exception ex)
            {
                //TODO: Loging an exeption to log viewer 
                Console.WriteLine(ex.Message);
            }

        }


        /// <summary>
        /// Synchronize calendars
        /// </summary>
        /// <param name="serviceS">Exchange Service binding to source server</param>
        /// <param name="serviceD">Exchange Service binding to destination server</param>
        /// <param name="users">Colletion of users which mailboxes will be synchronized</param>
        public static void SynchronizeCalendars(ExchangeService serviceS, ExchangeService serviceD, ref UsersCollection users)
        {

            ChangeCollection<ItemChange> icc;

            foreach (User user in users.List)
            {
                //Bind services to impersonated user
                serviceS.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, user.Mail);
                serviceD.ImpersonatedUserId = new ImpersonatedUserId(ConnectingIdType.SmtpAddress, user.Mail);

                if (!CheckUser(serviceD))
                    continue;

                //Read state from user file
                string sSyncState = user.SyncState;

                //Get state and save it to user File
                icc = serviceS.SyncFolderItems(new FolderId(WellKnownFolderName.Calendar),
                                               BasePropertySet.FirstClassProperties,
                                               null, 512,
                                               SyncFolderItemsScope.NormalItems, sSyncState);

                user.SyncState = icc.SyncState;

                foreach (ItemChange ic in icc)
                {
                    if (ic.ChangeType == ChangeType.Create)
                    {
                        ItemId destItemId = NewAppointment(serviceS, serviceD, ic);
                        user.AddConnectedItemIs(ic.ItemId.UniqueId, destItemId.UniqueId);
                    }
                    else if (ic.ChangeType == ChangeType.Update)
                    {
                        UpdateAppointment(serviceS, serviceD, ic, user.GetConnectedItemId(ic.ItemId.UniqueId));
                    }
                    else if (ic.ChangeType == ChangeType.Delete)
                    {
                        DeleteAppointment(serviceD, ic, user.GetConnectedItemId(ic.ItemId.UniqueId));
                        user.DeleteConnectedItemIds(ic.ItemId.UniqueId);
                    }
                }
            }
        }

        /// <summary>
        /// Checks if user have mailbox on destination server
        /// </summary>
        /// <param name="serviceD">Exchange service object which stores Impersonated account</param>
        /// <returns></returns>
        private static bool CheckUser(ExchangeService serviceD)
        {
            try
            {
                Appointment ap = new Appointment(serviceD);
                ap.Save();
                return true;
            }
            catch (Exception ex)
            {
                return false;
            }
        }

        /// <summary>
        /// Create new appointment on Destination Exchange Server
        /// </summary>
        /// <param name="serviceS">Exchange Service connection source</param>
        /// <param name="serviceD">Exchange Service connection destination</param>
        /// <param name="ic">Created item object</param>
        private static ItemId NewAppointment(ExchangeService serviceS, ExchangeService serviceD, ItemChange ic)
        {
            Appointment source = (Appointment)Item.Bind(serviceS, ic.ItemId);

            Appointment appointment = new Appointment(serviceD);
            appointment.Subject = source.Subject;
            appointment.Start = source.Start;
            appointment.End = source.End;
            appointment.Categories = source.Categories;
            appointment.Body = source.Body;
            appointment.Location = source.Location;
            appointment.Save();

            return appointment.Id;
        }

        /// <summary>
        /// Delete appointment on Destination Exchange Server
        /// </summary>
        /// <param name="serviceD">Exchange Service connection destination</param>
        /// <param name="ic">Deleted item object</param>
        /// <param name="destItemId"></param>
        private static void DeleteAppointment(ExchangeService serviceD, ItemChange ic, ItemId destItemId)
        {
            Appointment appointment = Appointment.Bind(serviceD, destItemId);
            appointment.Delete(DeleteMode.HardDelete);
        }

        /// <summary>
        /// Update appointment on Destination Exchange Server
        /// </summary>
        /// <param name="serviceS">Exchange Service connection source</param>
        /// <param name="serviceD">Exchange Service connection destination</param>      
        /// <param name="ic">Changed item object</param>
        private static void UpdateAppointment(ExchangeService serviceS, ExchangeService serviceD, ItemChange ic, ItemId destItemId)
        {
            Appointment source = Appointment.Bind(serviceS, ic.ItemId);
            Appointment appointment = Appointment.Bind(serviceD, destItemId);
            appointment.Subject = source.Subject;
            appointment.Body = source.Body;
            appointment.Start = source.Start;
            appointment.End = source.End;
            appointment.Categories = source.Categories;
            appointment.Location = source.Location;

            appointment.Update(ConflictResolutionMode.AlwaysOverwrite);
        }

        /// <summary>
        /// Searches all users who have mailboxes
        /// </summary>
        /// <returns>Collection of users and assosiated with them mailboxes</returns>
        private static UsersCollection GetUsers()
        {
            UsersCollection users = new UsersCollection();

            if (!File.Exists("Users.data"))  // if there's no cache
            {
                // Create File for cache

                Stream stream = File.Open("Users.data", FileMode.OpenOrCreate);
                BinaryFormatter formatter = new BinaryFormatter();

                //Init
                users.List = new List<User>();

                DirectoryEntry de = new DirectoryEntry(ConfigurationManager.AppSettings.Get("ActiveDirectoryAdress"));
                de.AuthenticationType = AuthenticationTypes.Secure;

                DirectorySearcher ds = new DirectorySearcher(de);
                ds.Filter = ConfigurationManager.AppSettings.Get("ActiveDirectoryFilter");
                ds.PropertiesToLoad.Add("displayName");
                ds.PropertiesToLoad.Add("mail");

                //Searches all users who have mailboxes
                SearchResultCollection results = ds.FindAll();

                if (results != null)
                {
                    foreach (SearchResult sr in results)
                    {
                        if (sr.Properties["displayname"].Count != 0 && sr.Properties["mail"].Count != 0)
                        {
                            string userName = sr.Properties["displayname"][0].ToString();
                            string userMail = sr.Properties["mail"][0].ToString();

                            users.List.Add(new User(userName, userMail));
                        }
                    }
                }

                formatter.Serialize(stream, users);
                stream.Close();
            }
            else
            {
                //Load cache
                Stream stream = File.Open("Users.data", FileMode.Open);
                BinaryFormatter formatter = new BinaryFormatter();
                users = (UsersCollection)formatter.Deserialize(stream);

                //TODO: Check AD for new Users
                DirectoryEntry de = new DirectoryEntry(ConfigurationManager.AppSettings.Get("ActiveDirectoryAdress"));
                de.AuthenticationType = AuthenticationTypes.Secure;

                DirectorySearcher ds = new DirectorySearcher(de);
                ds.Filter = ConfigurationManager.AppSettings.Get("ActiveDirectoryFilter");
                ds.PropertiesToLoad.Add("displayName");
                ds.PropertiesToLoad.Add("mail");

                //Searches all users who have mailboxes
                SearchResultCollection results = ds.FindAll();

                //
                if (results != null)
                {
                    foreach (SearchResult sr in results)
                    {
                        if (sr.Properties["displayname"].Count != 0 && sr.Properties["mail"].Count != 0)
                        {
                            if (!users.Exists(sr.Properties["displayname"][0].ToString()))  
                            {
                                //Add user to collection
                                string userName = sr.Properties["displayname"][0].ToString();
                                string userMail = sr.Properties["mail"][0].ToString();

                                users.List.Add(new User(userName, userMail));
                            }
                        }
                    }
                }
            }

            return users;
        }

        /// <summary>
        /// Saves state of users and their callendars
        /// </summary>
        /// <param name="collection">Collection of users which will be saved</param>
        private static void SaveState(UsersCollection users)
        {
            Stream stream = File.Open("Users.data", FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(stream, users);
            stream.Close();
        }

        /// <summary>
        /// Gets enumeratin of specific version of Exchange
        /// </summary>
        /// <param name="configKey"></param>
        /// <returns>Return version of Exchange Server</returns>
        private static ExchangeVersion GetExchangeVer(string configKey)
        {
            if ("2007_sp1" == ConfigurationManager.AppSettings.Get(configKey))
            {
                return ExchangeVersion.Exchange2007_SP1;
            }
            if ("2010" == ConfigurationManager.AppSettings.Get(configKey))
            {
                return ExchangeVersion.Exchange2010;
            }
            return ExchangeVersion.Exchange2010;    // By default return Exchange 2010
        }
    }
}
